﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lime49;

namespace ResourceBlender.Core {
	/// <summary>
    /// String parsing methods.
    /// </summary>
    public class StringParser {
        private int pos;
        private string internalData;

        public StringParser(string data) {
            internalData = data;
        }

        public StringParser(string startTag, string endTag, string data) {
            internalData = Parse(startTag, endTag, data);
        }


        /// <summary>
        /// Extracts data between two tags.
        /// </summary>
        /// <param name="startTag">The start tag.</param>
        /// <param name="endTag">The end tag.</param>
        /// <param name="data">The data.</param>
        /// <returns>The data extracted between the first occurence of startTag and the first endTag thereafter</returns>
        public static string Parse(string startTag, string endTag, string data) {
            if(startTag == null)
                throw new ArgumentNullException("startTag");

            if(endTag == null)
                throw new ArgumentNullException("endTag");

            if(data == null)
                throw new ArgumentNullException("data");

            int startTagLength = startTag.Length;
            int resultIdxStart = data.IndexOf(startTag);
            if(resultIdxStart < 0) throw new Exception(string.Format("Start tag {0} not found in string: {1}", startTag, data));
            resultIdxStart += startTagLength;

            int resultIdxEnd = data.IndexOf(endTag, resultIdxStart);
            if(resultIdxEnd < 0) throw new Exception(string.Format("End tag {0} not found in string: {1}", endTag, data));

            String result_string = HtmlUtils.HtmlDecode(data.Substring(resultIdxStart, resultIdxEnd - resultIdxStart));
            return result_string.Trim();
        }

        /// <summary>
        /// Extracts all data to the left of a tag.
        /// </summary>
        /// <param name="endTag">The tag.</param>
        /// <param name="data">The data.</param>
        /// <returns>All data to the left of the first occurence of the tag.</returns>
        public static string ExtractLeft(string endTag, string data) {
            if(endTag == null)
                throw new ArgumentNullException("endTag");

            if(data == null)
                throw new ArgumentNullException("data");

            int resultIdxEnd = data.IndexOf(endTag);
            if(resultIdxEnd < 0) throw new Exception(string.Format("End tag {0} not found in string: {1}", endTag, data));

            String result_string = HtmlUtils.HtmlDecode(data.Substring(0, resultIdxEnd));
            return result_string.Trim();
        }

        /// <summary>
        /// Extracts all data to the right of a tag.
        /// </summary>
        /// <param name="startTag">The start tag.</param>
        /// <param name="data">The data.</param>
        /// <returns>All data to the right of the first occurence of the tag.</returns>
        public static string ExtractRight(string startTag, string data) {
            if(startTag == null)
                throw new ArgumentNullException("startTag");

            if(data == null)
                throw new ArgumentNullException("data");

            int startTagLength = startTag.Length;
            int resultIdxStart = data.IndexOf(startTag);
            if(resultIdxStart < 0) throw new Exception(string.Format("Start tag {0} not found in string: {1}", startTag, data));
            resultIdxStart += startTagLength;

            String result_string = HtmlUtils.HtmlDecode(data.Substring(resultIdxStart));
            return result_string.Trim();
        }

        /// <summary>
        /// Removes text between all instances of two tags. The start and end tags do not need to be the same.
        /// </summary>
        /// <param name="startTag">The start tag.</param>
        /// <param name="endTag">The end tag.</param>
        /// <param name="data">The data.</param>
        /// <returns>The data with text between all <paramref name="startTag"/>s and <paramref name="endTag"/>s removed.</returns>
        /// <example>
        /// RemoveAll("<a>", "</a>", "<a>insideA</a><b>insideB</b> outside <a>insideA2<b>incideB2</b></a>")
        /// Returns:
        /// <b>insideB</b> outside <b>insideB2</b>
        /// </example>
        public static string RemoveAll(string startTag, string endTag, string data) {
            if(startTag == null)
                throw new ArgumentNullException("startTag");

            if(endTag == null)
                throw new ArgumentNullException("endTag");

            if(data == null)
                throw new ArgumentNullException("data");

            StringBuilder result = new StringBuilder();

            int pos = 0;
            int endTagLength = endTag.Length;
            int startTagIdx = data.IndexOf(startTag, pos);
            while(startTagIdx >= 0) {
                result.Append(data.Substring(pos, startTagIdx - pos));
                pos = data.IndexOf(endTag, startTagIdx);
                if(pos < 0) throw new Exception(string.Format("End tag {0} not found in string: {1}", endTag, data));
                pos += endTagLength;
                startTagIdx = data.IndexOf(startTag, pos);
            }
            result.Append(data.Substring(pos));
            return result.ToString();
        }

        /// <summary>
        /// Reads the text from the current position between the start and end tag.
        /// </summary>
        /// <param name="startTag">The start tag.</param>
        /// <param name="endTag">The end tag.</param>
        /// <param name="stopTag">The stop tag (will return <c>null</c> if found).</param>
        /// <returns>All text between the start and end tag (unless <paramref name="stopTag"/> is found.</returns>
        public string ReadItem(string startTag, string endTag, string stopTag) {
            if(startTag == null)
                throw new ArgumentNullException("startTag");

            if(endTag == null)
                throw new ArgumentNullException("endTag");

            if(stopTag == null)
                throw new ArgumentNullException("stopTag");

            int stopTagIdx = internalData.IndexOf(stopTag, pos);

            int startTagLength = startTag.Length;
            int resultIdxStart = internalData.IndexOf(startTag, pos);
            if(resultIdxStart < 0) throw new Exception(string.Format("Start tag {0} not found in string: {1}", startTag, internalData.Substring(pos)));

            if(stopTagIdx >= 0 && stopTagIdx < resultIdxStart) {
                return null;
            }


            resultIdxStart += startTagLength;

            int resultIdxEnd = internalData.IndexOf(endTag, resultIdxStart);
            if(resultIdxEnd < 0) throw new Exception(string.Format("End tag {0} not found in string: {1}", endTag, internalData.Substring(resultIdxStart)));

            if(stopTagIdx >= 0 && stopTagIdx < resultIdxEnd) {
                return null;
            }

            String result_string = internalData.Substring(resultIdxStart, resultIdxEnd - resultIdxStart);
            resultIdxEnd += endTag.Length;
            pos = resultIdxEnd;
            return result_string.Trim();
        }

        public string ReadItem(string startTag, string endTag) {
            return ReadItem(startTag, endTag, endTag);
        }

        public IEnumerable<string> ReadItemsList(string startTag, string endTag) {
            return ReadItemsList(startTag, endTag, "238742sdf89723sd9821"); //some unique end tag
        }

        /// <summary>
        /// Reads the items list.
        /// </summary>
        /// <param name="startTag">The start tag.</param>
        /// <param name="endTag">The end tag.</param>
        /// <param name="stopTag">The stop tag (will return <c>null</c> if found).</param>
        /// <returns>All text between the start and end tag (unless <paramref name="stopTag"/> is found.</returns>
        public IEnumerable<string> ReadItemsList(string startTag, string endTag, string stopTag) {
            //List<string> res = new List<string>();
            string item;
            while(internalData.IndexOf(startTag, pos) >= 0) {
                item = ReadItem(startTag, endTag, stopTag);
                if(string.IsNullOrEmpty(item))
                    break;
                yield return HtmlUtils.HtmlDecode(item);
                //res.Add(HtmlUtils.HtmlDecode(item));
            }
            //return res.ToArray();
        }

        /// <summary>
        /// Parses items between a start and end tag.
        /// </summary>
        /// <param name="startTag">The start tag.</param>
        /// <param name="endTag">The end tag.</param>
        /// <param name="data">The data to parse.</param>
        /// <returns>A list of strings between the specified start and end tags.</returns>
        public static IEnumerable<string> ParseItemsList(string startTag, string endTag, string data) {
            StringParser parser = new StringParser(data);
            return parser.ReadItemsList(startTag, endTag);
        }
    }
}
